﻿using System;
using System.Diagnostics;
using Xunit.Sdk;

namespace SubSpec
{
    public interface ISpecificationPrimitive
    {
        /// <summary>
        /// Indicate that execution of this delegate should be canceled after a specified timeout.
        /// </summary>
        /// <param name="timeoutMs">The timeout in milliseconds.</param>
        ISpecificationPrimitive WithTimeout( int timeoutMs );
    }

    internal class SpecificationPrimitive<T> : ISpecificationPrimitive
    {
        private readonly string _message;
        private readonly T _action;
        private int _timeoutMs;

        /// <summary>
        /// Initializes a new instance of the Context class.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="action"></param>
        public SpecificationPrimitive( string message, T action )
        {
            if (message == null)
                throw new ArgumentNullException( "message" );
            if (action == null)
                throw new ArgumentNullException( "action" );

            _message = message;
            _action = action;
        }

        public string Message
        {
            get { return _message; }
        }
        public T ActionDelegate
        {
            get { return _action; }
        }
        public int TimeoutMs
        {
            get { return _timeoutMs; }
        }

        public ISpecificationPrimitive WithTimeout( int timeoutMs )
        {
            _timeoutMs = timeoutMs;
            return this;
        }
    }

    /// <summary>
    /// Provides specializations for executing specification primitives.
    /// </summary>
    internal static class SpecificationPrimitiveExtensions
    {
        public static void Execute( this SpecificationPrimitive<Action> primitive )
        {
            Debug.Assert( primitive.ActionDelegate != null );

            if (primitive.TimeoutMs > 0)
            {
#if SILVERLIGHT
                IAsyncResult asyncResult = AsyncDelegatesCompatibility.WorkingBeginInvoke( primitive.ActionDelegate, null, null );
#else
                IAsyncResult asyncResult = primitive.ActionDelegate.BeginInvoke( null, null );
#endif

                if (!asyncResult.AsyncWaitHandle.WaitOne( primitive.TimeoutMs ))
                    throw new Xunit.Sdk.TimeoutException( primitive.TimeoutMs );
                else
                {
#if SILVERLIGHT
                    primitive.ActionDelegate.WorkingEndInvoke( asyncResult );
#else
                    primitive.ActionDelegate.EndInvoke( asyncResult );
#endif

                }
            }
            else
                primitive.ActionDelegate();
        }

        public static IDisposable Execute( this SpecificationPrimitive<ContextDelegate> primitive )
        {
            Debug.Assert( primitive.ActionDelegate != null );

            if (primitive.TimeoutMs > 0)
            {
#if SILVERLIGHT
                IAsyncResult asyncResult = AsyncDelegatesCompatibility.WorkingBeginInvoke( primitive.ActionDelegate, null, null );
#else
                IAsyncResult asyncResult = primitive.ActionDelegate.BeginInvoke( null, null );
#endif

                if (!asyncResult.AsyncWaitHandle.WaitOne( primitive.TimeoutMs ))
                    throw new Xunit.Sdk.TimeoutException( primitive.TimeoutMs );
                else
                {
#if SILVERLIGHT
                    return (IDisposable)primitive.ActionDelegate.WorkingEndInvoke( asyncResult );
#else
                    return primitive.ActionDelegate.EndInvoke( asyncResult );
#endif

                }
            }
            else
                return primitive.ActionDelegate();
        }
    }
}